package cn.thornbird.orgsync.repository.specification;

import cn.thornbird.orgsync.util.DateUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

public abstract class BaseSpecification<T> implements Specification<T> {

    private static final long serialVersionUID = 1L;

    protected Predicate createEqualPredicate(Root<T> root, CriteriaBuilder criteriaBuilder,
                                             String field, String value) {
        return createEqualPredicate(root, criteriaBuilder, field, value, false);
    }

    protected Predicate createEqualPredicate(Root<T> root, CriteriaBuilder criteriaBuilder,
                                             String field, String value, boolean caseSensitive) {
        Predicate predicate = null;
        if (!StringUtils.isEmpty(value)) {
            Path<String> path = root.get(field);
            if (caseSensitive) {
                predicate = criteriaBuilder.equal(path, value);
            } else {
                predicate = criteriaBuilder.equal(criteriaBuilder.lower(path), value.toLowerCase());
            }
        }
        return predicate;
    }

    protected Predicate createEqualPredicate(Root<T> root, CriteriaBuilder criteriaBuilder,
                                             String field, Boolean value) {
        Predicate predicate = null;
        if (value != null) {
            Path<String> path = root.get(field);
            predicate = criteriaBuilder.equal(path, value);
        }
        return predicate;
    }

    protected Predicate createEqualPredicate(Root<T> root, CriteriaBuilder criteriaBuilder,
                                             String field, Short value) {
        Predicate predicate = null;
        if (value != null) {
            Path<String> path = root.get(field);
            predicate = criteriaBuilder.equal(path, value);
        }
        return predicate;
    }

    protected Predicate createDatePredicates(Root<T> root, CriteriaBuilder criteriaBuilder, String field, Date value) {
        Predicate predicate = null;
        if (value != null) {
            predicate = createDatePredicates(root, criteriaBuilder, field, value, value);
        }
        return predicate;
    }

    protected Predicate createDatePredicates(Root<T> root, CriteriaBuilder criteriaBuilder, String field,
                                             Date startDate, Date endDate) {
        if (startDate == null) {
            startDate = DateUtil.createDate(1970, 0, 1);
        }
        if (endDate == null) {
            endDate = DateUtil.createDate(9999, 11, 31);
        }
        startDate = getDateRanges(startDate).get(0);
        endDate = getDateRanges(endDate).get(1);
        Path<Date> path = root.get(field);
        Predicate startPredicate = criteriaBuilder.greaterThanOrEqualTo(path, startDate);
        Predicate endPredicate = criteriaBuilder.lessThan(path, endDate);
        return criteriaBuilder.and(startPredicate, endPredicate);
    }

    protected Predicate createDatePredicates(Root<T> root, CriteriaBuilder criteriaBuilder,
                                             String field1, String field2, Date value) {
        Predicate predicate = null;
        if (value != null) {
            Path<Date> path1 = root.get(field1);
            Path<Date> path2 = root.get(field2);
            Date date = getDateRanges(value).get(0);
            Predicate startPredicate = criteriaBuilder.lessThanOrEqualTo(path1, date);
            Predicate endPredicate = criteriaBuilder.greaterThanOrEqualTo(path2, date);
            predicate = criteriaBuilder.and(startPredicate, endPredicate);
        }
        return predicate;
    }

    protected Predicate createFloatPredicates(Root<T> root, CriteriaBuilder criteriaBuilder, String field,
                                              Float minValue, Float maxValue) {
        Predicate predicate = null;
        Path<Float> path = root.get(field);
        if (minValue != null && maxValue != null) {
            Predicate predicate1 = criteriaBuilder.greaterThanOrEqualTo(path, minValue);
            Predicate predicate2 = criteriaBuilder.lessThanOrEqualTo(path, maxValue);
            predicate = criteriaBuilder.and(predicate1, predicate2);
        } else if (minValue != null) {
            predicate = criteriaBuilder.greaterThanOrEqualTo(path, minValue);
        } else if (maxValue != null) {
            predicate = criteriaBuilder.lessThanOrEqualTo(path, maxValue);
        }
        return predicate;
    }

    protected Predicate createInPredicate(Root<T> root, CriteriaBuilder criteriaBuilder, String field, String value) {
        Predicate predicate = null;
        if (!StringUtils.isEmpty(value)) {
            List<Predicate> predicates = new ArrayList<>();
            Path<String> path = root.get(field);
            List<String> values = Arrays.stream(value.trim().split(",")).collect(Collectors.toList());
            for (String v : values) {
                predicates.add(criteriaBuilder.equal(path, v));
            }
            predicate = criteriaBuilder.or(predicates.toArray(new Predicate[0]));
        }
        return predicate;
    }

    protected Predicate createLikePredicate(Root<T> root, CriteriaBuilder criteriaBuilder, String field, String value) {
        Predicate predicate = null;
        if (!StringUtils.isEmpty(value)) {
            Path<String> path = root.get(field);
            predicate = criteriaBuilder.like(criteriaBuilder.lower(path), "%" + value.toLowerCase() + "%");
        }
        return predicate;
    }

    protected List<Date> getDateRanges(Date date) {
        List<Date> range = new ArrayList<>();
        Date startDate = DateUtil.setTime(date, 0, 0, 0);
        Date endDate = DateUtil.addDays(startDate, 1);
        range.add(startDate);
        range.add(endDate);
        return range;
    }

    protected Predicate toKeywordPredicate(String keyword, List<String> fields, Root<T> root,
                                           CriteriaBuilder criteriaBuilder) {
        List<Predicate> predicates = new ArrayList<>();
        if (!StringUtils.isEmpty(keyword)) {
            keyword = "%" + keyword.toLowerCase() + "%";
            for (String field : fields) {
                predicates.add(criteriaBuilder.like(criteriaBuilder.lower(root.get(field)), keyword));
            }
            return criteriaBuilder.or(predicates.toArray(new Predicate[0]));
        }
        return null;
    }

    protected Predicate toPredicate(CriteriaBuilder criteriaBuilder, List<Predicate> predicates) {
        for (int i = predicates.size() - 1; i >= 0; i--) {
            if (predicates.get(i) == null) {
                predicates.remove(i);
            }
        }
        if (predicates.isEmpty()) {
            predicates.add(criteriaBuilder.conjunction());
        }
        return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
    }

}
